#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <errno.h>

#define N 1024
#define IGNSPACE(buf, pos) do {while(buf[pos] == ' ') pos++;}while(0)
#define NOREDIR 0
#define INREDIR 1
#define OUTREDIR 2
#define APPREDIR 3
int tmpfd; //临时文件描述符，用于恢复工作
char redirpath[N]; //重新向文件路径
char command[N]; //存放获取的命令
char* argv[N]; //指向拆分后的命令
const char* SEP = " "; // 命令选项间隔符
int redirtype = NOREDIR; //重定向类型
int exitcode; //进程退出码

const char* getUserName()
{
    return getenv("USER");
}

const char* getHostName()
{
    return getenv("HOSTNAME");
}

const char* getPwd()
{
    return getenv("PWD");
}

const char* getHomeDir()
{
    return getenv("HOME");
}


void checkReDir()
{
    int pos = 0;
    while(command[pos])
    {
        //下面所有的重定向都是在shell进程中重定向的，所以后面无论是内建命令还是非内建命令执行结束之后，都要将重定向恢复，所以要保证标准输入、输出、错误不能丢
        //因此需要创建临时文件，用它的fd保持标准输入、输出、错误流
        //为什么必须在shell中重定向，因为内建命令是shell亲自执行，所以必须shell中重定向，而非内建命令可以不用
        redirtype = NOREDIR;
        if(command[pos] == '<')
        {
            //输入重定向
            redirtype = INREDIR; 
            command[pos++] = '\0';
            IGNSPACE(command, pos); //跳过空格
            strcpy(redirpath, command+pos);
            int fd = open(redirpath, O_RDONLY);
            tmpfd = open(".tmp.txt", O_WRONLY | O_CREAT);
            dup2(0, tmpfd);
            dup2(fd, 0);
            break;
        }
        else if(command[pos] == '>')
        {
            if(command[pos+1] == '>')
            {
                //追加重定向
                redirtype = APPREDIR;
                command[pos] = '\0';
                pos += 2;
                IGNSPACE(command, pos);
                strcpy(redirpath, command+pos);
                int fd = open(redirpath, O_WRONLY | O_CREAT | O_APPEND, 0666);
                tmpfd = open(".tmp.txt", O_WRONLY | O_CREAT);
                dup2(1, tmpfd);
                dup2(fd, 1);
            }
            else
            {
                //输出重定向
                redirtype = OUTREDIR;
                command[pos++] = '\0';
                IGNSPACE(command, pos);
                strcpy(redirpath, command+pos);
                int fd = open(redirpath, O_WRONLY | O_CREAT | O_TRUNC, 0666);
                tmpfd = open(".tmp.txt", O_WRONLY | O_CREAT);
                dup2(1, tmpfd);
                dup2(fd, 1);
            }
            break;
        }
        else
        {
            pos++;
        }
    }
}


int interactive()
{
    printf("[%s@%s %s]$ ", getUserName(), getHostName(), getPwd());
    fgets(command, N, stdin);
    command[strlen(command)-1] = '\0'; // 消去'\n'
    if(strlen(command) == 0)
        return 0; 

    //拆分命令之前检查是否需要重定向,需要则重定向，再拆分执行
    checkReDir();

    if(command[0] == 'l' && command[0] == 'l')
        strcpy(command, "ls -a -l");


    // 指令的拆分
    int k = 0;
    argv[k++] = strtok(command, SEP);
    while(argv[k++] = strtok(NULL, SEP));

    //给ls指令添加颜色选项
    if(strcmp(argv[0], "ls") == 0)
    {
        argv[k-1] = (char*)"--color";
        argv[k] = NULL;
    }
    return 1;
}


int isBulidinCommand()
{
    // 需要当前进程亲自去执行的命令叫内建命令
    // echo、cd
    int ret = 0;
    if(strcmp(argv[0], "cd") == 0)
    {
        // cd or cd xxx
        ret = 1;
        char* target = argv[1];
        if(!target)
            target = (char*)getHomeDir();
        if(-1 == chdir(target))
            exitcode = 127;
        else
        {
            // cd跳转，环境变量PATH并没有联动改变，所有需要自己联动
            char cwd[N] = {0}, buf[N] = {0};
            char* p = getcwd(buf, N);
            sprintf(cwd, "PWD=%s", buf);
            putenv(cwd);
        }
    }
    else if(strcmp(argv[0], "echo") == 0)
    {
        // echo $? 输出进程退出码
        // echo $PATH 输出环境变量
        // echo 12345 输出字符串
        ret = 1;
        if(!argv[1])
        {
            printf("\n");
        }
        else
        {
            if(argv[1][0] == '$')
            {
                if(argv[1][1] == '?')
                    printf("%d\n", exitcode);
                else if(argv[1][1] != '\0')
                    printf("%s\n", getenv(argv[1]+1));
                else if(argv[1][1] == '\0')
                    printf("$\n");
            }
            else
            {
                int i = 1;
                while(argv[i])
                    printf("%s ", argv[i++]);
                printf("\n");
            }
        }
        exitcode = 0;
    }
    else if(strcmp(argv[0], "export") == 0 && argv[1])
    {
        char* env = calloc(N, sizeof(char));
        strcpy(env, argv[1]);
        putenv(env);
        exitcode = 0;
    }
    return ret;
}

void execute()
{
    if(isBulidinCommand()) //判断是否是内建命令，是则执行，否则返回1
    {

    }
    else
    {
        pid_t pid = fork();
        if(pid == 0)
        {
            //child
            execvp(argv[0], argv); //函数执行成功没有返回值，因为程序都被替换了，怎么可能再往下执行呢，没有意义，失败返回-1，errno被设置
            exit(errno);
        }
        //father
        int status;
        waitpid(pid, &status, 0); //0表示阻塞等待，WNOHANG非阻塞等待
        exitcode = WEXITSTATUS(status);
    }
    // 当执行执行结束之后，应该将之前的重定向修改回来
    if(redirtype == INREDIR)
    {
        dup2(tmpfd, 0);
        close(tmpfd);
        remove(".tmp.txt");
    }
    else if(redirtype == OUTREDIR || redirtype == APPREDIR)
    {
        dup2(tmpfd, 1);
        close(tmpfd);
        remove(".tmp.txt");
    }
}

int main()
{
    while(1)
    {
        // 获取命令并拆解
        if(interactive() == 0)
            continue;

        // 执行命令
        execute();
    }
    return 0;
}
